#ifndef __SEMAPHORE_VECTOR_HPP__
#define __SEMAPHORE_VECTOR_HPP__

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#include <cerrno>
#include <vector>
#include <sstream>

#include "exception.hpp"

namespace ipc {

typedef union {
	int val;
	struct semid_ds* buf;
	unsigned short* array;
} semnum;

#define SEM_DEFAULT_FLAG 0

//TODO: REVISAR DOCUMENTACIÓN.
/**
 * Encapsulamiento de la funcionalidad del semaforo system V.
 */
class semaphore_vector {
	protected:
		int id;	// Identificador del semaphore.
		key_t key;
		size_t size;

//		semaphore_vector (const semaphore_vector& other);
//		semaphore_vector& operator= (const semaphore_vector& other);

		/// Solo para debug.
		int getCurrentCount(int semaphoreNumber=0) const {
			return semctl(this->id, semaphoreNumber, GETVAL);
		}

	public:
		semaphore_vector(const key_t& key, size_t size = 1) {
			this->key = key;
			this->id = 0;
			this->size = size;
		}

		/***
		 * @return El identificador del semaphore.
		 */
		int getId() const {
			return this->id;
		}

		key_t getKey() const {
			return this->key;
		}

		size_t getSize() const {
			return this->size;
		}

		operator bool() const {
			return (this->id > 0);
		}
	
		int get (int mode) {
			int error = 0;
			if (this->id <= 0) {
				int semId = semget(this->key,this->size, mode);
				if (semId > 0)
					this->id = semId;
				else
					error = errno;
			}
			return error;
		}

		int init (int init_value, size_t number = 0) {
			int error = 0;
			if (this->id > 0) {
				semnum init;
				init.val = init_value;
				error = semctl ( this->id,number,SETVAL,init);
			} else
				error = -1;
			return error;
		}

		/**
		 * Inicializa todos los semaforos del grupo.
		 * @param initialValues Arrays con los valores de los semaforos a inicializar.
		 */
		int init(const std::vector<unsigned short>& initialValues, bool default_value = false) {
			int error = 0;
		
			if ( this->id > 0) {
				semnum init;

				size_t largo = 0;
				//std::auto_ptr<unsigned short> semsarray(new unsigned short[this->size]);
				unsigned short* semsarray = new unsigned short[this->size];
				if (!initialValues.empty()) {
					size_t largo = this->size;
					if (initialValues.size() < largo)
						largo = initialValues.size();
					for (size_t i=0; i<largo; ++i)
						semsarray[i] = initialValues.at(i) ? 1:0;
				}
				for (size_t i=largo; i<this->size; ++i)
					semsarray[i] = default_value ? 1:0;
			
				error = semctl(this->id, 0, SETALL, init);

				if (error != 0)
					error = semctl(this->id, 0, IPC_RMID);

				delete[](semsarray);
			}

			return error;
		}

		/**
		 * Intenta adquirir el semaforo con numero espeficiado, si no esta disponible (contador <= 0) se bloquea.
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @param flags Banderas para la operacion.
		 */
		int wait(int semaphoreNumber = 0, int flags = SEM_DEFAULT_FLAG) {
			struct sembuf waitOperation;
			waitOperation.sem_num = semaphoreNumber;
			waitOperation.sem_op  = -1;
			waitOperation.sem_flg = flags;
			
			return semop (this->id, &waitOperation, 1);
		}

		/**
		 * Intenta adquirir el semaforo con numero espeficiado, si no esta disponible (contador <= 0) se bloquea hasta que el contador sea exacamente cero.
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @param flags Banderas para la operacion.
		 */
		int waitExclusive(int semaphoreNumber = 0, int flags = SEM_DEFAULT_FLAG) {
			struct sembuf waitOperation;
			waitOperation.sem_num = semaphoreNumber;
			waitOperation.sem_op  = 0;
			waitOperation.sem_flg = flags;

			return semop (this->id, &waitOperation, 1);
		}

		/**
		 * Libera el recurso restringido por el semaforo especificado (incrementa en uno el contador).
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @param flags Banderas para la operacion.
		 */
		int signal(int semaphoreNumber = 0, int flags = SEM_DEFAULT_FLAG) {
			struct sembuf signalOperation;
			signalOperation.sem_num = semaphoreNumber;
			signalOperation.sem_op  = 1;
			signalOperation.sem_flg = flags;
		
			return semop (this->id, &signalOperation, 1);
		}

		/**
		 * Retorna cantidad de procesos esperando por el aumento en el contador del semaforo especificado.
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @return Cantidad de procesos esperando por el aumento del valor del semaforo.
		 */
		int getCurrentWaitingProcessAmountForCounterIncrease(int semaphoreNumber = 0) {
			return semctl(this->id, semaphoreNumber, GETNCNT);
		}

		/**
		 * Retorna cantidad de procesos esperando para que el semaforo espeficicado valga 0.
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @return Cantidad de procesos esperando para que el semaforo valga 0.
		 */
		int getCurrentWaitingProcessAmountForZeroCounter(int semaphoreNumber = 0) {
			return semctl(this->id, semaphoreNumber, GETZCNT);
		}

		/**
		 * Retorna el identificador del ultimo proceso que realizo una operacion sobre el semaforo especificado.
		 * @param semaphoreNumber Numero de semaforo, dentro del grupo, sobre el cual realizar la operacion.
		 * @return Id del ultimo proceso que opero sobre el semaforo.
		 */
		int getLastOperatingProcessId(int semaphoreNumber = 0) {
			return semctl(this->id, semaphoreNumber, GETPID);
		}

		/**
		 * Libera los recursos creados por la clase, eliminando el grupo de semaforos creado en caso que no exista ninguna proceso esperando por el mismo.
		 */
		int free() {
			size_t waiting = 0;
			// Solo se libera el semaforo si nadie lo esta esperando.			
			size_t n = this->getSize();
			for (size_t i = 0; i < n; i++) {
				int forZero = getCurrentWaitingProcessAmountForZeroCounter(i);
				int forIncr = getCurrentWaitingProcessAmountForCounterIncrease(i);
				if ( (forZero >= 0) || (forIncr >= 0) )
					waiting+= forZero+forIncr;
			}

			if (waiting == 0)
				return destroy();
		}

		int destroy() {
			return semctl(this->id, 0, IPC_RMID);
		}

		/// Solo para debug.
		friend std::ostream& operator<< (std::ostream& os, const semaphore_vector& sv) {
			std::stringstream ss;

			size_t n = sv.getSize();
			ss << sv.getId() << " N: " << n << " Valores: [ ";
			for (size_t i=0; i<n; ++i)
				ss << sv.getCurrentCount(i) << " ";
			ss << "]";
			os << ss;
			return os;
		}

		std::string str () const {
			std::stringstream ss;

			size_t n = getSize();
			ss << getId() << " N: " << n << " Valores: [ ";
			for (size_t i=0; i<n; ++i)
				ss << getCurrentCount(i) << " ";
			ss << "]";
			return ss.str();
		}

}; // class semaphore_vector;

// Adapta el acceso a un elemento del vector de semaforos.
class semaphore {
	protected:
		size_t number;
		semaphore_vector group;
	public:
		semaphore () : group(0,-1) {}

		// El semaforo debe tener como minimo number+1 elementos.
		semaphore (const key_t& key, size_t number) : group(key,number+1) {
			this->number = number;
		}
		semaphore (const semaphore_vector& g, size_t number) : group(g) {
			this->number = number;
		}

		inline int getId() const {
			return group.getId();
		}
		inline key_t getKey() const {
			return group.getKey();
		}
		inline size_t getNumber() const {
			return number;
		}

		inline virtual int get (int mode) {
			return group.get(mode);
		}

		inline virtual int destroy() {
			return group.destroy();
		}

		inline virtual int init(bool init_value) {
			return group.init(init_value,number);
		}
		inline virtual	int signal(int flags = SEM_DEFAULT_FLAG) {
			return group.signal(number,flags);
		}
		inline virtual int wait(int flags = SEM_DEFAULT_FLAG) {
			return group.wait(number,flags);
		}
}; // semaphore

// Excepcion utilizada por semaphore_with_exceptions
class semaphore_exception : public exception {
	public:
		semaphore_exception (int semId, key_t key, const std::string& message) : exception(semId,key,message) {}

		std::string getClass() const {
			return "Semaphore";
		}
}; // class semaphore_exception

// Adapta el acceso a un semaforo, y agrega el uso de excepciones para reportar errores.
class semaphore_with_exceptions : public semaphore {
	public:
		semaphore_with_exceptions (const key_t& key, size_t number) : semaphore(key,number) {}
		semaphore_with_exceptions (const semaphore_vector& g, size_t number) : semaphore(g,number) {}

		inline int init(bool init_value) throw(semaphore_exception) {
			if (group.init(init_value,number) != 0)
				throw semaphore_exception(group.getId(),group.getKey(),"init");
			return 0;
		}
		inline int signal(int flags = SEM_DEFAULT_FLAG) throw(semaphore_exception) {
			if (group.signal(number,flags) != 0)
				throw semaphore_exception(group.getId(),group.getKey(),"signal");
			return 0;
		}
		inline int wait(int flags = SEM_DEFAULT_FLAG) throw(semaphore_exception) {
			if (group.wait(number,flags) != 0)
				throw semaphore_exception(group.getId(),group.getKey(),"wait");
			return 0;
		}

		int get(int mode) throw(semaphore_exception) {
			if (group.get(mode) != 0)
				throw semaphore_exception(group.getId(),group.getKey(),"get");
			return 0;
		}

		inline int destroy() throw(semaphore_exception) {
			if (group.destroy() != 0)
				semaphore_exception(group.getId(),group.getKey(),"destroy");
			return 0;
		}

}; // class semaphore_with_exceptions

//class lock {
//	protected:
//		semaphore& sem;

//	public:
//		lock (semaphore& s) : sem(s) {
//			sem.wait();
//		}
//		~lock() {
//			sem.signal();
//}; // class lock

} // namespace ipc

#endif /* __SEMAPHORE_VECTOR_HPP__ */

